<!--通知栏组件-->
<!--  
  传入参数：
    isShow: 是否显示，默认true ---Boolean
    isOpenScrollable: 开启溢出自动滑动动画，默认 true ---Boolean
    showLeftIcon: 是否展示左边标志,默认true ---Boolean
    showRightIcon: 是否展示右边标志,默认false ---Boolean
    boxCss: 最外层额外css ---String
    leftIconCss: 左边标志盒子额外样式 ---String
    rightIconCss:右边标志盒子额外样式 ---String
    contentCss: 展示内容盒子额外样式 ---String
    transverseBoxCss: 横向模式盒子额外css (只有在横向模式，即mode为transverse时有效) ---String
    portraitBoxCss: 纵向模式盒子额外css (只有在纵向模式，即mode为portrait时有效) ---String
    mode: 模式 横向 transverse （默认）； 纵向 portrait ---String
    content: 展示内容数组，每个成员为一个对象，text属性为当前成员展示文字内容；className属性为当前成员自定义样式 ---Array
             例子：[{text:'展示名称',className:'item'}]
    leftIcon: 左边标志（图片地址） ---String
    rightIcon: 右边标志（图片地址） ---String
    delay: 移动延迟，单位秒，默认 1 (只有在横向模式，即mode为transverse时有效) ---Number
    speed: 滚动速率 (px/s)，默认60 (只有在横向模式，即mode为transverse时有效) ---Number
    rollingTime: 滚动时间，单位秒，默认 1 (只有在纵向模式，即mode为portrait时有效) ---Number
    scrollTime: 滚动间隔，单位秒，默认 1.5 (只有在纵向模式，即mode为portrait时有效) ---Number
    
  交互方法：
    contentItemClicks:渲染成员个体点击函数，使用父级回调函数，传递当前点击成员的位置信息
    noticeBarRight:右边按键点击函数，使用父级回调函数

  插槽:
    noticeBarLeft:左边盒子插槽
    transverseItem:横向模式展示的成员插槽
    portraitItem:纵向模式展示的成员插槽
    noticeBarRight:右边盒子插槽
  
  注意事项:
    1、若是使用了插槽，那么对应位置上的内容将会被代替，并且相关的方法也会同时失效
    2、本组件对于css高度自由，故基础样式的内容除了宽高，其他的建议别去顶替，其他的样式可以根据自己的需要去进行自我进行处理添加
    3、本组件提供的基础样式并不能展示全面，故需要个人对源码或在提供的css占位上进行处理添加
    4、本组件的动画效果样式建议别进行更改，若是有需求可以根据源码进行修改即可
    5、本组件监听的是内容渲染数组content，但是vue2有监听数组的缺陷，建议在不确定渲染的内容数量或者更新内容时，先对渲染的content属性置空，然后再异步的方式进动态添加属性
-->
<!-- ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————— -->

<template>
  <div class="components_noticeBar" :class="boxCss" v-if="isShow">
    <!-- 左边盒子插槽 -->
    <slot name="noticeBarLeft">
      <!-- 左边默认展示标志 -->
      <div
        class="components_noticeBar-left"
        :class="leftIconCss"
        v-if="showLeftIcon"
        :style="{ backgroundImage: `src(${leftIcon})` }"
      ></div>
    </slot>

    <!-- 展示内容 -->
    <!-- --------------------------------------------------------------------------------------------- -->
    <div
      class="components_noticeBar-content"
      :class="contentCss"
      ref="components_noticeBar_content"
    >
      <!-- 横向模式 -->
      <span
        class="components_noticeBar-content-transverse"
        :class="`${transverseBoxCss} ${contentTransverse}`"
        ref="components_noticeBar_content_transverse"
        v-if="mode === 'transverse'"
      >
        <!-- 横向模式展示的成员插槽 -->
        <slot name="transverseItem">
          <!-- 默认展示形式 -->
          <span
            v-for="(value, index) in content"
            :key="index"
            :class="value.className"
            @click.stop="contentItem(index)"
          >
            {{ value.text }}
          </span>
        </slot>
      </span>

      <!-- 纵向模式 -->
      <!-- ==================================================================================== -->
      <div
        class="components_noticeBar-content-portrait"
        :class="portraitBoxCss"
        ref="components_noticeBar_content_portrait"
        v-if="mode === 'portrait'"
      >
        <!-- 纵向模式展示的成员插槽 -->
        <slot name="portraitItem">
          <!-- 默认展示形式 -->
          <div
            v-for="(value, index) in [...content, content[0]]"
            :key="index"
            :class="value.className ? value.className : 'portraitItems'"
            @click.stop="contentItem(index)"
          >
            {{ value.text }}
          </div>
        </slot>
      </div>
    </div>

    <!-- 右边展示按键 -->
    <!-- --------------------------------------------------------------------------------------------- -->
    <!-- 右边盒子插槽 -->
    <slot name="noticeBarRight">
      <!-- 右边默认展示标志 -->
      <div
        class="components_noticeBar-right"
        :class="rightIconCss"
        v-if="showRightIcon"
        :style="{ backgroundImage: `src(${rightIcon})` }"
        @click.stop="noticeBarRightBtn"
      ></div>
    </slot>
  </div>
</template>

<script>
// 接口+方法引用
// ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

export default {
  // 传参
  props: {
    // 功能开关
    // ---------------------------------------------------------------------------------------------
    // 是否显示，默认true
    isShow: {
      type: [Boolean],
      default: true,
    },

    // 开启溢出自动滑动动画，默认 true
    isOpenScrollable: {
      type: [Boolean],
      default: true,
    },

    // 是否展示左边标志,默认true
    showLeftIcon: {
      type: [Boolean],
      default: true,
    },

    // 是否展示右边标志,默认false
    showRightIcon: {
      type: [Boolean],
      default: false,
    },

    // 额外css
    // ---------------------------------------------------------------------------------------------
    // 最外层额外css
    boxCss: {
      type: [String],
      default: '',
    },

    // 左边标志盒子额外样式
    leftIconCss: {
      type: [String],
      default: '',
    },

    // 展示内容盒子额外样式
    contentCss: {
      type: [String],
      default: '',
    },

    // 横向模式盒子额外css (只有在横向模式，即mode为transverse时有效)
    transverseBoxCss: {
      type: [String],
      default: '',
    },

    // 纵向模式盒子额外css (只有在纵向模式，即mode为portrait时有效)
    portraitBoxCss: {
      type: [String],
      default: '',
    },

    // 右边标志盒子额外样式
    rightIconCss: {
      type: [String],
      default: '',
    },

    // 功能展示属性
    // ---------------------------------------------------------------------------------------------
    // 模式 横向 transverse （默认）； 纵向 portrait
    mode: {
      type: [String],
      default: 'transverse',
    },

    // 展示内容数组
    content: {
      type: [Array],
      default() {
        return []
      },
    },

    // 左边标志
    leftIcon: {
      type: [String],
      default: '',
    },

    // 右边标志
    rightIcon: {
      type: [String],
      default: '',
    },

    // 移动延迟，单位秒，默认 1 (只有在横向模式，即mode为transverse时有效)
    delay: {
      type: [Number],
      default: 1,
    },

    // 滚动速率 (px/s)，默认60 (只有在横向模式，即mode为transverse时有效)
    speed: {
      type: [Number],
      default: 60,
    },

    // 滚动时间，单位秒，默认 1 (只有在纵向模式，即mode为portrait时有效)
    rollingTime: {
      type: [Number],
      default: 1,
    },

    // 滚动间隔，单位秒，默认 1.5 (只有在纵向模式，即mode为portrait时有效)
    scrollTime: {
      type: [Number],
      default: 1.5,
    },
  },

  // 参数变量声明区域
  // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  data() {
    return {
      contentTransverse: '', // 盒子动效css承接值
      contentTransverseTimer: 0, // 盒子动效css转变定时器承接值
      portraitItemTimer: 0, // 纵向模式成员移动定时器承接值
      displacementCoordinate: 0, // 纵向模式下盒子移动的坐标
    }
  },

  // 监听函数使用区域
  // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  watch: {
    // 对传入的 展示内容数组 进行监听
    content: {
      handler(res) {
        // 如果没有内容渲染数据 或者 是 并没有开启 溢出隐藏开关
        if (!res.length || !this.isOpenScrollable) {
          // 则直接弹出不需要执行以下数据
          return
        }

        // 先清理切换css的定时器，防止产生bug
        // ====================================================================================
        clearTimeout(this.contentTransverseTimer)
        clearInterval(this.contentTransverseTimer)
        clearTimeout(this.portraitItemTimer)

        // 因为要操作dom节点的数据，所以在异步中可以获取到执行完成后的具体内容，防止出错
        // ====================================================================================
        setTimeout(() => {
          // 执行初始化函数,根据模式确定执行那个初始化方法
          this.mode === 'transverse' && this.transverseInit()
          this.mode === 'portrait' && this.portraitInit()
        })
      },
      // 深度监听
      deep: true,
    },
  },

  // 无内存函数使用区域
  // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  methods: {
    // 横向模式初始化函数
    transverseInit() {
      // 获取需要的盒子节点信息
      // ====================================================================================
      let contentBox = this.$refs.components_noticeBar_content
      let transverseBox = this.$refs.components_noticeBar_content_transverse

      // 判断 横向模式盒子的实际宽度是否已经超过了父级盒子
      if (transverseBox.offsetWidth <= contentBox.offsetWidth) {
        // 如果没有超过则直接弹出，不用执行以下操作
        return
      }

      // 获取此次移动距离情况用于计算需要的动画时间
      // ====================================================================================
      let itemMovingDistance = transverseBox.offsetWidth //  获取首次需要移动的距离即 横向模式盒子 实际宽度
      let needTime = itemMovingDistance / this.speed // 当前移动距离 除以 每秒移动的px数，得到需要的秒数
      // 事先计算好循环css时动画时间，即 横向模式盒子+他父级盒子的全部的宽度 除以 每秒移动的px数
      let loopDistanceTime =
        (itemMovingDistance + contentBox.offsetWidth) / this.speed

      // 给横向模式进行绑定首次动画样式信息
      // ====================================================================================
      transverseBox.style.animationDelay = this.delay + 's' // 首次动画需要有延迟时间，即传入的延迟参数
      transverseBox.style.animationDuration = needTime + 's' // 绑定动画完成的实际时间
      this.contentTransverse = 'firstCss' // 添加首次的动画时间

      // 给横向模式进行绑定循环动画样式信息（此定时器时间为首次动画的时间+延迟的时间）
      // ====================================================================================
      this.contentTransverseTimer = setTimeout(() => {
        transverseBox.style.animationDelay = 0 + 's' // 循环动画不需要延迟信息
        transverseBox.style.animationDuration = loopDistanceTime + 's' // 循环动画时间为特定的时间
        this.contentTransverse = 'loopCss' // 改变播放动画的css
      }, (needTime + this.delay) * 1000)
    },

    // 纵向模式初始化函数
    // ---------------------------------------------------------------------------------------------
    portraitInit() {
      // 如果渲染成员只有一个，则直接弹出，不用进行移动
      if (this.content.length === 1) {
        return
      }
      // 获取需要的盒子节点信息
      // ====================================================================================
      let contentBox = this.$refs.components_noticeBar_content
      let portraitBox = this.$refs.components_noticeBar_content_portrait

      // 获取此次移动距离情况+过渡时间css属性绑定
      // ====================================================================================
      let itemMovingDistance = contentBox.offsetHeight // 获取到盒子的高度作为每次移动的固定距离

      // 开启循环定时器（定时器时间为渲染时间和间隔时间的综合）
      // ====================================================================================
      this.contentTransverseTimer = setInterval(() => {
        portraitBox.style.transitionDuration = this.rollingTime + 's' // 绑定每次过渡的的距离时间
        this.displacementCoordinate += 1 // 纵向模式下盒子移动的坐标自增1
        // 纵向移动盒子本次移动的距离为 自增坐标数 乘以 移动的固定距离
        portraitBox.style.top =
          -(itemMovingDistance * this.displacementCoordinate) + 'px'

        /*
         * 判断此时的自增值是否和内容渲染数组的长度是相等的，因为此时渲染的成员会比本来的渲染数组多一个首位成员位于末尾
         * 所以此时数组的长度值正好为移动值的最大值相同
         */
        if (this.displacementCoordinate === this.content.length) {
          // 触发单次定时器，将渲染成员属性进行复原，并且此定时器的时间为过渡动画完成时间，进行无缝衔接
          this.portraitItemTimer = setTimeout(() => {
            // 为了防错，清除定时器
            clearTimeout(this.portraitItemTimer)
            portraitBox.style.transitionDuration = 0 + 's'
            this.displacementCoordinate = 0
            portraitBox.style.top = 0
          }, this.rollingTime * 1000)
          return
        }
      }, (this.scrollTime + this.rollingTime) * 1000)
    },

    // 成员点击函数
    // ---------------------------------------------------------------------------------------------
    contentItem(index) {
      // 父级回调函数，传递点击的成员信息
      this.$emit('contentItemClicks', index)
    },

    // 右边区域点击函数
    // ---------------------------------------------------------------------------------------------
    noticeBarRightBtn() {
      // 父级回调函数
      this.$emit('noticeBarRight')
    },
  },
}
</script>

<style scoped>
/* 通知栏组件 */
.components_noticeBar {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
}

/* 左边标志 */
/* --------------------------------------------------------------------------------------------- */
.components_noticeBar-left {
  width: 16px;
  height: 16px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

/* 展示内容 */
/* --------------------------------------------------------------------------------------------- */
.components_noticeBar-content {
  flex: 1;
  overflow: hidden;
  position: relative;
}

/* 横向模式 */
/* ==================================================================================== */
.components_noticeBar-content-transverse {
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}

/* 横向模式展示的成员 */
.components_noticeBar-content-transverse > span {
  white-space: nowrap;
}

/* 纵向模式 */
/* ==================================================================================== */
.components_noticeBar-content-portrait {
  width: 100%;
  position: absolute;
  top: 0;
  transition-property: all;
  transition-timing-function: linear;
}

/* 纵向模式展示成员 */
.components_noticeBar-content-portrait > div {
  width: 100%;
}

/* 纵向成员默认样式 */
.portraitItems {
  height: 24px;
  line-height: 24px;
}
/* 右边标志 */
/* --------------------------------------------------------------------------------------------- */
.components_noticeBar-right {
  width: 16px;
  height: 16px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

/* 动画内容 */
/* --------------------------------------------------------------------------------------------- */
/* 首次动画css */
.firstCss {
  transform: translateX(0);
  animation-name: firstAnimation;
  animation-iteration-count: 1;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}

@keyframes firstAnimation {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-100%);
  }
}

/* 循环动画css */
.loopCss {
  transform: translateX(0);
  left: 100%;
  animation-name: loopAnimation;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

@keyframes loopAnimation {
  0% {
    transform: translateX(0);
    left: 100%;
  }
  100% {
    transform: translateX(-100%);
    left: 0;
  }
}
</style>
